#include "../source/db.hpp"
#include "../source/logger.hpp"
#include "../source/matcher.hpp"
#include "../source/online.hpp"
#include "../source/room.hpp"
#include "../source/server.hpp"
#include "../source/util.hpp"
#include "../source/session.hpp"

#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>

#define HOST "127.0.0.1"    // 设置只能本地登录
#define PORT 3306           // 默认就是3306
#define USER "root"         // root 用户登录
#define PASS "ax020913"     // root 登录 mysql 的密码
#define DBNAME "gobang"     // 我们刚刚创建的 gobang 数据库

typedef websocketpp::server<websocketpp::config::asio> wsserver_t;

wsserver_t wssrv;

// 用户数据库
// register页面注册时候的 _ut.insert(req_json);
// log页面登录时候的 _ut.insert(req_json);
user_table _ut(HOST, USER, PASS, DBNAME, PORT);

session_manager _sm(&wssrv);
online_manager _om;
room_manager _rm(&_ut, &_om);
matcher _mm(&_rm, &_ut, &_om);



/*-------------------------------- http 协议请求的处理  -------------------------------------*/

// http协议：服务端向浏览器client端的回复
void http_resp(wsserver_t::connection_ptr conn, bool result, const std::string &reason, websocketpp::http::status_code::value code){
    Json::Value recv_json;
    std::string recv_body;
    recv_json["result"] = result;
    recv_json["reason"] = reason;

    // static bool serialize(const Json::Value &root, std::string &str)
    json_util::serialize(recv_json, recv_body);
    conn->set_body(recv_body);
    conn->set_status(code);
    conn->append_header("Content-Type", "application/json");
    return ;
}

// web 根目录
#define _web_root "./wwwroot/"
void file_handler(wsserver_t::connection_ptr conn){
    websocketpp::http::parser::request req = conn->get_request();
    std::string method = req.get_method();
    std::string uri = req.get_uri();
    
    // 如果请求的是一个wbe根目录
    // (类似于这样的输入搜索：http://43.143.88.137:8080/)，我们直接给它返回一个 register.html 让它注册
    std::string realpath = _web_root + uri;
    // std::cout << uri << std::endl;
    if(realpath.back() == '/')   
        realpath += "register.html";

    // static bool file_util::read(请求的文件路径名, 文件读取到的string& body);
    std::string body;
    bool ret = file_util::read(realpath, body);
    // 读取 register.html 文件失败 --> 我们的 server 服务端错误 --> 文件都读取不出来 --> 404 页面
    if(ret == false){
        body += "<html>";
        body += "<head>";
        body += "<meta charset='UTF-8'/>";
        body += "</head>";
        body += "<body>";
        body += "<h1>404 NOT FOUND</h1>";
        body += "</body>";
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::value::not_found);
        return;
    } 
    // 读取成功了
    conn->set_body(body);
    conn->set_status(websocketpp::http::status_code::value::ok);
    return;
}

// 处理来自register页面的请求
void reg(wsserver_t::connection_ptr conn){
    websocketpp::http::parser::request req = conn->get_request();
    std::string reg_body = conn->get_request_body();

    // 将 client 的 req_json 反序列化一下
    Json::Value reg_json;
    // static bool unserialize(const std::string& str, Json::Value &root)
    bool ret = json_util::unserialize(reg_body, reg_json);
    if(ret == false){
        DLOG("json 反序列化失败！");
        return http_resp(conn, false, "json 反序列化失败！", websocketpp::http::status_code::value::bad_request);
    }
    // 先判断是否要登录
    if(reg_json["goto_login"] == true)
        return http_resp(conn, true, "跳转登录界面成功", websocketpp::http::status_code::value::ok);

    // 先判断 reg_json 的信息是否正确
    if(reg_json["username"].isNull() || reg_json["password"].isNull()){
        DLOG("username password 信息有问题");
        return http_resp(conn, false, "username password 信息有问题", websocketpp::http::status_code::value::bad_request);
    }

    // _ut.insert(Json::Value reg_json);
    ret = _ut.insert(reg_json);
    if(ret == false){
        DLOG("insert 数据库失败");
        return http_resp(conn, false, "insert 数据库失败", websocketpp::http::status_code::value::bad_request);
    }

    return http_resp(conn, true, "insert 成功", websocketpp::http::status_code::value::ok);
}

// 处理来自login页面的请求
void login(wsserver_t::connection_ptr conn){
    websocketpp::http::parser::request req = conn->get_request();
    std::string reg_body = conn->get_request_body();

    // 1. 将 client 的 req_json 反序列化一下
    Json::Value reg_json;
    // static bool unserialize(const std::string& str, Json::Value &root)
    bool ret = json_util::unserialize(reg_body, reg_json);
    if(ret == false){
        DLOG("json 反序列化失败！");
        return http_resp(conn, false, "json 反序列化失败！", websocketpp::http::status_code::value::bad_request);
    }
    // 2. 先判断是否要登录
    if(reg_json["goto_register"] == true)
        return http_resp(conn, true, "跳转注册界面成功", websocketpp::http::status_code::value::ok);

    // 3. 先判断 reg_json 的信息是否正确
    if(reg_json["username"].isNull() || reg_json["password"].isNull()){
        DLOG("username password 信息有问题");
        return http_resp(conn, false, "username password 信息有问题", websocketpp::http::status_code::value::bad_request);
    }

    // 4. _ut.login(Json::Value reg_json);
    ret = _ut.login(reg_json);
    if(ret == false){
        DLOG("login 数据库失败");
        return http_resp(conn, false, "login 数据库失败", websocketpp::http::status_code::value::bad_request);
    }

    // 5. 登录成功进入hall页面，得创建session，设置过期时间
    uint64_t uid = reg_json["id"].asUInt64();
    session_ptr ssp = _sm.create_session(uid, LOGIN);
    if(ssp.get() == nullptr){
        DLOG("会话创建失败");
        return http_resp(conn, false, "会话创建失败", websocketpp::http::status_code::value::bad_request);
    }
    _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);    // 给会话创建生命周期
    
    // 6. 设置响应头部：Set-Cookie,将sessionid通过cookie返回
    std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());
    conn->append_header("Set-Cookie", cookie_ssid);

    return http_resp(conn, true, "login 成功", websocketpp::http::status_code::value::ok);
}

// 从 cookie 中获取 ssid
bool get_cookie_val(const std::string& cookie_str, const std::string &key, std::string &val){
    // Cookie: SSID=XXX; path=/; 
    // 1. 以 ; 作为间隔，对字符串进行分割，得到各个单个的cookie信息
    std::string sep = "; ";
    std::vector<std::string> cookie_arr;
    string_util::split(cookie_str, sep, cookie_arr);
    for(auto str: cookie_arr){
        // 2. 对单个cookie字符串，以 = 为间隔进行分割，得到key和val   
        std::vector<std::string> temp_arr;
        string_util::split(str, "=", temp_arr);
        if(temp_arr.size() != 2) continue;
        if(temp_arr[0] == key){
            val = temp_arr[1];
            return true;
        }
    }
    return false;
}

// 处理来自hall页面的请求
void info(wsserver_t::connection_ptr conn){
    // 1. 获取请求信息中的Cookie，从Cookie中获取ssid
    Json::Value err_resp;
    std::string cookie_str = conn->get_request_header("Cookie");
    if(cookie_str.empty() == true)   // 如果没有cookie，返回错误：没有cookie信息，让客户端重新登录
        return http_resp(conn, false, "找不到cookie信息，请重新登录", websocketpp::http::status_code::bad_request);

    // 1.5. 从cookie中取出ssid
    std::string ssid_str;
    bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);
    if(ret == false){
        // cookie中没有ssid，返回错误：没有ssid信息，让客户端重新登录
        return http_resp(conn, false, "找不到ssid信息，请重新登录", websocketpp::http::status_code::bad_request);
    }

    // 2. 在session管理中查找对应的会话信息
    session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
    if(ssp.get() == nullptr){
        // 没有找到session，则认为登录已经过期，需要重新登录
        return http_resp(conn, false, "找不到session信息，请重新登录", websocketpp::http::status_code::bad_request);  // 会话session过期了
    }

    // 3. 从数据库中取出用户信息，进行序列化发送给客户端
    uint64_t uid = ssp->get_user();  // 取出用户 id
    Json::Value user_info;
    ret = _ut.select_by_id(uid, user_info);
    if(ret == false){
        // 获取用户信息失败，返回错误：找不到用户信息
        return http_resp(conn, false, "用户信息获取失败", websocketpp::http::status_code::bad_request);
    }

    // 因为这里是http协议请求，所以server是不能主动send的。下面的websocket协议是可以send的
    std::string body;
    json_util::serialize(user_info, body);
    conn->set_body(body);
    conn->set_status(websocketpp::http::status_code::ok);
    conn->append_header("Content-Type", "application/json");

    // 4. 刷新session的过期时间
    _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);   // 得重新刷新一下的哦
}

// http协议部分的请求(分发,分情况)处理
void http_callback(wsserver_t* wssrv, websocketpp::connection_hdl hdl){
    // 
    wsserver_t::connection_ptr conn = wssrv->get_con_from_hdl(hdl);
    //
    websocketpp::http::parser::request req = conn->get_request();
    //
    const std::string method = req.get_method();
    const std::string uri = req.get_uri();
    if(method == "POST" && uri == "/reg")          // 处理来自register页面的请求
        return reg(conn);
    else if(method == "POST" && uri == "/login")   // 处理来自login页面的请求
        return login(conn);
    else if(method == "GET" && uri == "/info")      // 处理来自hall页面的请求
        return info(conn);
    else 
        return file_handler(conn);                 // 获取静态页面资源请求
}



/*-------------------------------- websocket 协议请求的处理  -------------------------------------*/


void wsopen_callback(wsserver_t* wssrv, websocketpp::connection_hdl hdl){
    
}

void wsclose_callback(wsserver_t* wssrv, websocketpp::connection_hdl hdl){
    
}

void wsmsg_callback(wsserver_t* wssrv, websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg){
    
}

int main(void){
    // 1. 实例化server对象
    // websocketpp::server<websocketpp::config::asio> wsserver;
    // 因为上面的类型过长了，所以在上面的ypedef一下
    // wsserver_t wssrv;
    // 2. 设置日志等级
    // 这里在我们先不设置日志了
    wssrv.set_access_channels(websocketpp::log::alevel::none);
    // 3. 初始化asio调度器
    wssrv.init_asio();
    wssrv.set_reuse_addr(true);  // 设置端口复用

    // 设置回调函数
    wssrv.set_http_handler(std::bind(http_callback, &wssrv, std::placeholders::_1));
    wssrv.set_open_handler(std::bind(wsopen_callback, &wssrv, std::placeholders::_1));
    wssrv.set_close_handler(std::bind(wsclose_callback, &wssrv, std::placeholders::_1));
    wssrv.set_message_handler(std::bind(wsmsg_callback, &wssrv, std::placeholders::_1, std::placeholders::_2));
    // 5. 设置监听端口
    wssrv.listen(8080);   // 8085 .... 也是可以的
    // 6. 开始获取新连接
    wssrv.start_accept();
    // 7. 启动服务器
    wssrv.run();

    return 0;
}